﻿using System;
using System.Linq;
using System.Collections.Generic;

namespace RNA_Libraries
{
  public class Sequence
  {
    #region Private fields

    private readonly uint[] m_freq;
    private readonly string m_value;
    private readonly Random m_rnd = new Random();

    #endregion

    #region Public interface

    public Sequence(string data = "")
    {
      m_value = data ?? string.Empty;
      var ll = LengthSelfTrunc;
      m_value = ll > 5 ? FinishSubstring(Length - LengthSelfTrunc) : m_value;
      m_freq = new uint[m_value.Length];
      for (var f = 0; f < m_freq.Length; f++)
      {
        m_freq[f] = 1;
      }
    }

    public Sequence(Sequence s) 
    {
      this.m_freq = s.m_freq;
      m_value = s.m_value;
    }

    public int Length
    {
      get { return m_value.Length; }
    }

    public uint Frequency 
    { 
      get 
      {
        var l = new List<uint>(m_freq);
        return l.Min();
      } 
    }

    public char this[int i]
    {
      get { return m_value[i]; }
    }

    public uint F(int i)
    {
      return m_freq[i];
    }

    public string FinishSubstring(int l)
    {
      return l > Length ? null : m_value.Substring(Length - l, l);
    }

    public string StartSubstring(int l)
    {
      return l > m_value.Length ? null : m_value.Substring(0, l);
    }

    public static Sequence  operator + (Sequence arg1, Sequence arg2){
      var l = arg1.EdgeLabel(arg2);
      var result = new Sequence(arg1.m_value + arg2.FinishSubstring(arg2.Length - l));
      for (var i = 0; i < arg1.Length - l; i++)
      {
        result.m_freq[i] = arg1.m_freq[i];
      }
      for (var i = arg1.Length - l; i < arg1.Length; i++)
      {
        result.m_freq[i] = arg1.m_freq[i] + arg2.m_freq[i - (arg1.Length - l)];
      }
      for (var i = arg1.Length; i < arg1.Length - l + arg2.Length; i++)
      {
        result.m_freq[i] = arg2.m_freq[i - (arg1.Length - l)];
      }
      return result;
    }

    public int EdgeLabel(Sequence seq)
    {
      if (this.m_value == seq.m_value) return Length;
      int l = 1;
      int max = 0;
      while (FinishSubstring(l) != null)
      {
        if (FinishSubstring(l) == seq.StartSubstring(l))
        {
          max = l;
        }
        l++;
      }
      return max;
    }

    private int LengthSelfTrunc
    {
      get
      {
        int l = 1;
        int max = 0;
        while (l < Length - 1)
        {
          if (FinishSubstring(l) == StartSubstring(l))
          {
            max = l;
          }
          l++;
        }
        return max;
      }
    }

    public List<Sequence> Split(int seqLimit, int layersCount)
    {
      List<Sequence> res = new List<Sequence>(seqLimit * layersCount);
      for (int i = 0; i < layersCount; i++)
      {
        res.AddRange(SubSplit(seqLimit, i * seqLimit / layersCount));
      }
      return res;
    }

    #endregion

    #region Implementation

    private List<Sequence> SubSplit(int limit, int offset)
    {
      var result = new List<Sequence>(100);
      
      var i = 0;
      do
      {
        var step = m_rnd.Next(limit / 3 * 2, limit);
        if (i+step >= Length)
        {
          result.Add(new Sequence(FinishSubstring(step)));
          return result;
        }
        result.Add(new Sequence(m_value.Substring(i, step)));
        i += step;
      } while (true);
    }

    #endregion

    #region Overrides

    public bool Equals(Sequence other)
    {
      if (ReferenceEquals(null, other)) return false;
      if (ReferenceEquals(this, other)) return true;
      return Equals(other.m_value, m_value);
    }

    public override bool Equals(object obj)
    {
      if (ReferenceEquals(null, obj)) return false;
      if (ReferenceEquals(this, obj)) return true;
      if (obj.GetType() != typeof(Sequence)) return false;
      return Equals((Sequence)obj);
    }

    public static bool operator ==(Sequence left, Sequence right)
    {
      return Equals(left, right);
    }

    public static bool operator !=(Sequence left, Sequence right)
    {
      return !Equals(left, right);
    }

    public override int GetHashCode()
    {
      return (m_value != null ? m_value.GetHashCode() : 0);
    }

    public override string ToString()
    {
      return m_value;
    }

    #endregion
  }
}
